/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package mux

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"sync"
	"time"
	"strings"
)

const QueryKey = "language"

// I18nDataProvider 提供 i18n 数据
type I18nDataProvider interface {
	// 读取 i18n 数据, "abbr" 是读取语言的简写名, "key" 是读取的数据名, 通常与模板名对应
	GetData(abbr, key string) interface{}
}

// 默认从文件中读取 i18n 数据, 也可以通过函数 SetI18nData() 来设置 i18n 数据来源
var provider I18nDataProvider = newI18nFileProvider(Env.Dir.I18n)

// 文件数据读取器
type i18nFileProvider struct {
	dir    string
	data   map[string]map[string]interface{}
	locker *sync.RWMutex
}

func (pvd *i18nFileProvider) GetData(abbr, key string) interface{} {
	var i interface{}
	if !Env.Server.Debug {
		pvd.locker.RLock()
		i, _ = pvd.data[key][abbr]
		pvd.locker.RUnlock()
	}
	if i == nil {
		dir := filepath.Join(pvd.dir, key)
		file := filepath.Join(dir, abbr+".json")
		_, err := os.Stat(file)
		if os.IsNotExist(err) {
			if Env.Server.Debug {
				err = os.MkdirAll(dir, 0777)
				if err != nil {
					panic(err)
				}
				ioutil.WriteFile(file, []byte("{}"), 0777)
			} else {
				panic(fmt.Errorf("i18n file [%s] not exist", file))
			}
		} else {
			data, err := ioutil.ReadFile(file)
			if err != nil {
				panic(err)
			}
			err = json.Unmarshal(data, &i)
			if err != nil {
				panic(err)
			}
			if !Env.Server.Debug {
				pvd.locker.Lock()
				if pvd.data[key] == nil {
					pvd.data[key] = map[string]interface{}{abbr: i}
				} else {
					pvd.data[key][abbr] = i
				}
				pvd.locker.Unlock()
			}
		}
	}
	return i
}

func newI18nFileProvider(dir string) *i18nFileProvider {
	return &i18nFileProvider{
		dir:    dir,
		data:   make(map[string]map[string]interface{}, 20),
		locker: &sync.RWMutex{},
	}
}

func SetI18nData(p I18nDataProvider) {
	provider = p
}

// I18N 后置中间件为每个页面生成指向当前页面的语言列表, 用户的语言信息保存于 cookie 中,
// 禁止搜索引擎设置 cookie, 以让搜索引擎从语言列表中映射指定语言的页面.
var I18N AfterHandler = func(ctx *Context) {
	if provider == nil {
		ctx.ErrWarning(errors.New("i18n data provider is nil"))
	}
	if ctx.Method == http.MethodGet {
		if len(ctx.views) > 0 {
			isSE := isSearchEngine(ctx)
			abbr := GetLanguageAbbr(ctx)
			name := GetLanguageName(abbr)
			for _, view := range ctx.views {
				ctx.With(view, provider.GetData(abbr, view))
			}
			var lists []map[string]string
			u := ctx.URL
			raw, _ := url.ParseQuery(u.RawQuery)
			for _, l := range Env.Languages.Lists {
				if l.Abbr != abbr {
					ur := &url.URL{
						Scheme:     u.Scheme,
						Opaque:     u.Opaque,
						User:       u.User,
						Host:       u.Host,
						Path:       u.Path,
						RawPath:    u.RawPath,
						ForceQuery: u.ForceQuery,
						Fragment:   u.Fragment,
					}
					if isSE {
						// http://orivil.com
						if Env.Languages.Default == l.Abbr {
							raw.Del(QueryKey)
						}
					} else {
						// http://orivil.com?language=en
						raw.Set(QueryKey, l.Abbr)
					}
					ur.RawQuery = raw.Encode()
					lists = append(lists, map[string]string{
						"abbr": l.Abbr,
						"name": l.Name,
						"url":  ur.String(),
					})
				}
			}
			ctx.With(QueryKey, map[string]interface{} {
				"current_name": name,
				"current_abbr": abbr,
				"lists":   lists,
			})
			if !isSE {
				ctx.SetCookie(&http.Cookie{
					Name:    QueryKey,
					Value:   abbr,
					Path:    "/",
					Expires: expires,
				})
			}
		}
	}
}

var expires = time.Now().Add(time.Hour * 24 * 30 * 12) // 1 year

// 语言列表数据模型
var listSchema = map[string]interface{}{
	// 当前语言名称
	"current_name": "English",
	"current_abbr": "en",

	// 可选择的语言列表
	"lists": []map[string]string{
		{
			"abbr": "zh_CN",
			"name": "简体中文",
			// url 根据当前请求的 url 自动生成, 实现无跳转, 对爬虫友好的多语言页面
			"url":  "https://orivil.com/xx?language=zh_CN",
		},
	},
}

// 获取当前语言全称
func GetLanguageName(abbr string) string {
	if abbr == "" {
		abbr = Env.Languages.Default
	}
	for _, l := range Env.Languages.Lists {
		if l.Abbr == abbr {
			return l.Name
		}
	}
	return abbr
}

// 获取当前语言简写
func GetLanguageAbbr(ctx *Context) string {
	lang := ctx.Query().Get(QueryKey)
	if lang == "" {
		cookie, _ := ctx.Cookie(QueryKey)
		if cookie != nil {
			lang = cookie.Value
		} else {
			lang = Env.Languages.Default
		}
	}
	return lang
}

var spiders = []string{
	"Baiduspider",
	"Bingbot",
	"Googlebot",
	"iaskspider",
	"sogou",
	"Sosospider",
	"Yahoo",
	"YodaoBot",
	"YoudaoBot",
	"360Spider",
	"HaoSouSpider",
}

// TODO: 待完善
func isSearchEngine(ctx *Context) bool {
	ua := ctx.Header().Get("User-Agent")
	for _, spider := range spiders {
		if strings.Contains(ua, spider) {
			return true
		}
	}
	return false
}